package dm.bsi;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.zookeeper.server.quorum.Election;
import org.dom4j.DocumentException;

import indi.zjh.dp.db.Session;
import indi.zjh.dp.error.CommandExecuteException;
import indi.zjh.dp.error.UnsuportedFieldException;
import indi.zjh.dp.orm.Relation;
import indi.zjh.dp.type.String128;
import indi.zjh.dp.util.SystemCmd;

public class Container extends LogNode {
	public static final int NOT_RUNNING = 0; // 容器挂掉, 但是可以查到状态
	public static final int RUNNING = 1; // 容器正常运行
	public static final int UNKNOWN = 2; // 节点超时, 容器超时, 状态未知
	
	public static final int ROLE_WORKER = 0; // 无状态的工作节点
	public static final int ROLE_MASTER = 1; // 主节点
	public static final int ROLE_SLAVE = 2; // 从节点
	public static final int ROLE_SENTINEL = 3; // 控制监控节点
	
	protected String128 oid = new String128();
	protected int containerPort = 0;
	protected int dockerPort = 0;
	protected int state = NOT_RUNNING;
	protected int role = ROLE_WORKER;
	
	protected Relation image = new Relation(Image.class);
	protected Relation node = new Relation(Node.class);
	protected Relation service = new Relation(Service.class);
	
	/**
	 * 获取关联的镜像对象
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Image getImage(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.image.size() == 0)
			return null;
		long imageid = this.image.getIds()[0];
		Image image = (Image)session.getById(Image.class, imageid);
		return image;
	}
	
	/**
	 * 容器重启导致的端口发生更新
	 * @param session
	 * @return: 如果更新了, 返回true
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public boolean refreshDockerPort(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		Node node = this.getNode(session);
		// 获取docker分配端口
		String cmd = "ssh root@"+node.getIp()+" docker ps -a --format='{{.Names}}#{{.Ports}}'|grep "+this.getOid();
		String response = SystemCmd.execCMD(cmd, 5).trim();
		
		//Log.info(session, node, cmd+":"+response);
		//CONTAINER_2#0.0.0.0:32769->8090/tcp
		int assignedPort = 0;
		try
		{
			if(!response.contains("->"))
			{
				Log.failure(session, node, cmd+":"+response);
				return false;
			}
			String portStr = response.split("#")[1].split("->")[0].split(":")[1];
			assignedPort = Integer.parseInt(portStr);
		}
		catch(Exception e)
		{
					
		}
		
		if(this.getDockerPort() != assignedPort)
		{
			Log.info(session, node, "容器后端口改变"+assignedPort);
			this.setDockerPort(assignedPort);
			session.save(this).commit();
			return true;
		}
		return false;
	}
	
	/**
	 * 重启容器
	 * @param session
	 * @throws Exception 
	 * @throws DocumentException 
	 * @throws UnsuportedFieldException 
	 */
	public void restart(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		Node node = this.getNode(session);
		// 获取docker分配端口
		String cmd = "ssh root@"+node.getIp()+" docker restart "+this.getOid();
		String response = SystemCmd.execCMD(cmd, 30);
		Log.info(session, node, "重启容器"+this.getOid()+","+cmd+":"+response);
		
		cmd = "ssh root@"+node.getIp()+" docker ps -a --format='{{.Names}}#{{.Ports}}'|grep "+this.getOid();
		response = SystemCmd.execCMD(cmd, 5).trim();
		
		Log.info(session, node, "restart,"+cmd+":"+response);
		//CONTAINER_2#0.0.0.0:32769->8090/tcp
		int assignedPort = 0;
		try
		{
			String portStr = response.split("#")[1].split("->")[0].split(":")[1];
			assignedPort = Integer.parseInt(portStr);
		}
		catch(Exception e)
		{
					
		}
		Log.info(session, node, "重启容器后分配的新端口"+assignedPort);
		this.setDockerPort(assignedPort);
		this.setState(Container.RUNNING);
		
		session.save(this).commit();
	}
	
	/**
	 * 启动容器
	 * @param session
	 * @param node
	 * @param image
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public void start(Session session, Deployment deployment, Node node, Image image, boolean isPortSpecify, HashMap<String, String> env) throws UnsuportedFieldException, DocumentException, Exception
	{
		// 处理端口
		if(isPortSpecify)
		{
			this.setContainerPort(deployment.getCurContainerPort());
			deployment.setCurContainerPort(deployment.getCurContainerPort() + 1);
			if(env != null)
			{
				env.put("PORT", this.getContainerPort() + "");
			}
		}
		else
		{
			this.setContainerPort(deployment.getContainerPort());
		}
		
		
		if(this.getId() == 0)
			session.save(this);
		this.setOid(deployment.getName()+"_"+this.getId());
		Service service = deployment.getService(session);
		
		// 处理环境变量
		String envStr = "";
		if(env != null && env.size() > 0)
		{
			Iterator<Map.Entry<String, String>> iter = env.entrySet().iterator();
			while(iter.hasNext())
			{
				Map.Entry<String, String> entry = iter.next();
				envStr += " -e " + entry.getKey() + "=" + entry.getValue() + " ";
			}
		}
		
		String cmd = "ssh root@"+node.getIp()+" docker run -d --dns="+node.getDockerGatewayIp()+" --name="+this.getOid()+" -p "+this.containerPort+" " + envStr + image.getName();
		String response = SystemCmd.execCMD(cmd, 60);
		Log.info(session, deployment, cmd+":"+response);
		// 获取docker分配端口
		cmd = "ssh root@"+node.getIp()+" docker ps -a --format='{{.Names}}#{{.Ports}}'|grep "+this.getOid();
		response = SystemCmd.execCMD(cmd, 5).trim();
		Log.info(session, deployment, "start,"+cmd+":"+response);
		//CONTAINER_2#0.0.0.0:32769->8090/tcp
		int assignedPort = 0;
		try
		{
			String portStr = response.split("#")[1].split("->")[0].split(":")[1];
			assignedPort = Integer.parseInt(portStr);
		}
		catch(Exception e)
		{
			
		}
		
		this.dockerPort = assignedPort;
		this.getImage().add(image);
		image.getContainer().add(this);
		this.getNode().add(node);
		node.getContainers().add(this);
		service.getContainers().add(this);
		this.getService().add(service);
		this.setState(Container.RUNNING);
		session.save(this).save(image).save(node).save(deployment).commit();
	}
	
	/**
	 * 获取关联的Node对象
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Node getNode(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.node.size() == 0)
			return null;
		long nodeid = this.node.getIds()[0];
		Node node = (Node)session.getById(Node.class, nodeid);
		return node;
	}
	
	/**
	 * 检测容器的状态
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public boolean isRunning(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		int ret = RUNNING;
		if(this.oid.string.isEmpty())
		{
			ret = NOT_RUNNING;
		}
		Node node = this.getNode(session);
		if(node == null)
		{
			ret = UNKNOWN;
		}
		//docker ps --no-trunc --format='{{.ID}}#{{.Names}}#{{.Ports}}#{{.Status}}#{{.Image}}'
		String cmd = "ssh root@"+node.getIp()+" docker ps -a --no-trunc --format='{{.Names}}#{{.Status}}' | grep "+this.oid;
		
		String response = null;
		try
		{
			response = SystemCmd.execCMD(cmd, 5).toLowerCase();
		}
		catch(CommandExecuteException e)
		{
			ret = UNKNOWN;
		}
		if(ret == RUNNING)
		{
			if(response != null && response.contains("#up"))
				ret = RUNNING;
			else
				ret = NOT_RUNNING;
		}
		
		// 状态发生变化, 入库
		if(this.state != ret)
		{
			this.state = ret;
			session.save(this);
		}
		
		return ret == RUNNING;
	}
	
	/**
	 * 在关联的节点上删除容器
	 * @param session
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public void destory(Session session, Service service) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.oid.string.isEmpty())
			return;
		Node node = this.getNode(session);
		if(node == null)
			return;
		
		boolean isDelete = false;
		Deployment deployment = service.getDeployment(session);
		
		try
		{
			String cmd = "ssh root@"+node.getIp()+" 'docker logs --tail=64 "+this.oid+"'";
			String response = SystemCmd.execCMD(cmd, 60);
			response = StringEscapeUtils.escapeSql(response);
		}
		catch(CommandExecuteException e)
		{
			Log.failure(session, deployment, "容器退出删除, "+StringEscapeUtils.escapeSql(e.getMessage()));
			Log.failure(session, node, "容器退出删除, "+StringEscapeUtils.escapeSql(e.getMessage()));
		}
		
		try
		{
			String cmd = "ssh root@"+node.getIp()+" 'docker rm -f "+this.oid+"'";
			SystemCmd.execCMD(cmd, 60);
			
			isDelete = true;
			
		}
		catch(CommandExecuteException e)
		{
			if(e.getMessage().contains("No such container"))
			{
				isDelete = true;
			}
			else if(e.getMessage().contains("No route to host"))
			{
				isDelete = true;
			}
		}
		
		if(isDelete)
		{
			service.getContainers().remove(this);
			this.getService().remove(service);
			this.delete();
			session.save(this);
			session.save(service);
		}
		
	}

	public static String toDictString(Method m)
	{
		if(m.getName().equals("getImage"))
		{
			return "获取产生该容器的镜像";
		}
		else if(m.getName().equals("refreshDockerPort"))
		{
			return "刷新docker分配给容器的对外端口";
		}
		else if(m.getName().equals("restart"))
		{
			return "重启容器";
		}
		else if(m.getName().equals("start"))
		{
			return "启动容器";
		}
		else if(m.getName().equals("getNode"))
		{
			return "获取容器运行的节点对象";
		}
		else if(m.getName().equals("isRunning"))
		{
			return "获取容器的运行状态";
		}
		else if(m.getName().equals("destory"))
		{
			return "销毁删除容器";
		}

		return LogNode.toDictString(m);
	}

	public static String toDictString(Field f)
	{
		if(f.getName().equals("image"))
		{
			return "容器关联的镜像";
		}
		else if(f.getName().equals("oid"))
		{
			return "容器在docker里的唯一标识码";
		}
		else if(f.getName().equals("state"))
		{
			return "容器状态, 0-未就绪, 1-运行, 2-未知(超时)";
		}
		else if(f.getName().equals("containerPort"))
		{
			return "容器里应用的端口";
		}
		else if(f.getName().equals("dockerPort"))
		{
			return "Docker为容器应用分配的映射端口";
		}
		else if(f.getName().equals("role"))
		{
			return "容器节点的角色, 包括worker:0, master:1, slave:2, sential:3";
		}
		else if(f.getName().equals("image"))
		{
			return "关联的镜像";
		}
		else if(f.getName().equals("node"))
		{
			return "容器运行的物理节点";
		}
		else if(f.getName().equals("service"))
		{
			return "容器支撑的服务";
		}
		return LogNode.toDictString(f);
	}
	
	
	
	@Override
	public String toString() {
		return "Container [oid=" + oid + ", containerPort=" + containerPort + ", dockerPort=" + dockerPort + ", state="
				+ state + ", role=" + role + ", image=" + image + ", node=" + node + ", service=" + service + ", _id="
				+ _id + ", _v=" + _v + "]";
	}

	public int getRole() {
		return role;
	}

	public void setRole(int role) {
		this.role = role;
	}

	public int getContainerPort() {
		return containerPort;
	}

	public void setContainerPort(int containerPort) {
		this.containerPort = containerPort;
	}
	
	public int getDockerPort() {
		return dockerPort;
	}

	public void setDockerPort(int dockerPort) {
		this.dockerPort = dockerPort;
	}
	
	public Relation getNode() {
		return node;
	}
	
	public Relation getImage() {
		return image;
	}
	
	public int getState() {
		return state;
	}
	public void setState(int state) {
		this.state = state;
	}
	public Relation getService() {
		return service;
	}
	
	public String getOid() {
		return oid.string;
	}
	public void setOid(String oid) {
		this.oid.string = oid;
	}
	
}
